home *** CD-ROM | disk | FTP | other *** search
- /* MicrosecondTimer.c */
- /*
- * Time measurement.
- * Copyright © 1991 Martin Minow. All Rights Reserved
- *
- * This function creates a high-resolution "time of day"
- * timer that is (or, at least, ought to be) synchronized
- * with the system time of day value. It uses the
- * new time manager calls.
- *
- * In order to keep our timer in reasonable synchronization
- * with the system time of day, we shadow that value at
- * each time-of-day trap.
- *
- * Usage:
- * InitializeMicrosecondTimer();
- * Call this -- once -- when your program starts. It
- * installs the timer interrupt routine. It returns
- * noErr if successful, or unimpErr if the Extended
- * Time Manager is not supported on this system.
- *
- * Important: if you are using the Think C ANSI
- * and/or console library, be sure to call
- * InitializeMicrosecondTimer before calling any
- * stdio or console routines. Otherwise, your program
- * may crash on exit under certain ill-defined
- * circumstances.
- *
- * CancelMicrosecondTimer()
- * This must be called before your application exits.
- * InitializeMSecTimer() establishes an exit handeler
- * to force its call, so you needn't worry about it.
- *
- * GetEpoch(MicrosecondEpoch *result)
- * Call this to get the time of day. The result
- * consists of a time (seconds) value that is
- * intended to track GetTimeOfDay exactly, extended
- * by the number of microseconds past this second.
- *
- * DeltaTime(
- * MicrosecondEpoch *startTime,
- * MicrosecondEpoch *endTime
- * signed long *difference
- * )
- * Compute the difference between two extended
- * time values, returning the result in the third
- * parameter (as a signed number of microseconds).
- * The result will be positive if time2 is later
- * than time1. DeltaTime returns TRUE if the
- * absolute value of the difference (in seconds)
- * is less than 35 minutes (a signed longword can
- * resolve a 34 minute interval). (You might want
- * to redo this to return a double-precision format
- * value, rather than a longword.)
- *
- * EpochToString(
- * MicrosecondEpoch *epoch
- * Str255 result
- * )
- * Convert an extended time value to a fixed-width,
- * fixed-format Pascal string "hh:mm:ss.fraction".
- *
- * Although the code has not been tested under MPW, it
- * ought to port easily: just re-do the asm stuff.
- *
- * Acknowledgements:
- * Parts of the time manager calls are based on a
- * timing module in the MacDTS FracApp demo program
- * by Keith Rollin an Bo3b Johnson.
- *
- * The exit handler is based on similar code in the
- * atexit() function in the Think C support library.
- */
-
- #include <GestaltEqu.h>
- #ifndef THINK_C
- #include <SysEqu.h>
- #endif
- #include <Timer.h>
- #include <Traps.h>
- #include "MicrosecondTimer.h"
-
- /*
- * This is needed to establish an exit trap handler.
- */
- typedef void (*MyProcPtr)(void);
- typedef struct {
- short jmp;
- MyProcPtr function;
- } JumpVector;
-
- static void *oldExitVector;
- static JumpVector *jumpVector;
- static void TimerExitHandler(void);
-
- #define MILLION (1000000L)
-
- /*
- * This is a time manager record, extended to include a
- * shadow copy of the system time of day value that is
- * updated once a second.
- */
- typedef struct TimeInfoRecord {
- TMTask TMTask; /* The task record */
- unsigned long epoch; /* Time of day info */
- } TimeInfoRecord, *TimeInfoPtr;
-
- static TimeInfoRecord gTimeInfo;
- #define TIME (gTimeInfo.TMTask)
- static long gOverheadTime;
- static pascal void TimeCounter(void);
-
- static void Concat(StringPtr dst, StringPtr src);
-
- /*
- * Install a timer interrupt procedure.
- */
- OSErr
- InitializeMicrosecondTimer()
- {
- long timeOfDay;
- long gestaltResult;
- OSErr status;
-
- if (TIME.tmAddr != NULL)
- status = noErr; /* Already installed */
- else {
- status = Gestalt(
- gestaltTimeMgrVersion,
- &gestaltResult
- );
- if (status == noErr
- && gestaltResult < gestaltExtendedTimeMgr)
- status = unimpErr;
- if (status == noErr) {
- /*
- * Install a trap handler for ExitToShell
- */
- oldExitVector =
- (void *) GetTrapAddress(_ExitToShell);
- if (ROM85 >= 0) {
- SetTrapAddress(
- (long) TimerExitHandler,
- _ExitToShell
- );
- }
- else {
- /*
- * Install a trap handler
- * in the system heap.
- */
- jumpVector = (JumpVector *)
- NewPtrSys(sizeof (JumpVector));
- if (jumpVector == NULL) {
- status = memFullErr;
- goto exit;
- }
- else {
- jumpVector->jmp = 0x4EF9;
- jumpVector->function =
- TimerExitHandler;
- SetTrapAddress(
- (long) jumpVector,
- _ExitToShell
- );
- }
-
- }
- /*
- * Install the time manager task and
- * start it rolling.
- */
- TIME.tmAddr = (ProcPtr) TimeCounter;
- InsXTime(&TIME);
- /*
- * Align our timer to the system's
- */
- timeOfDay = Time;
- do {
- gTimeInfo.epoch = Time;
- } while (timeOfDay == gTimeInfo.epoch);
- /*
- * We should really do this a bunch
- * of times and take the minimum.
- * gOverheadTime measures the amount
- * of time the PrimeTime/RmvTime sequence
- * requires, See the discussion in IM-VI.
- */
- PrimeTime(&TIME, -MILLION);
- RmvTime(&TIME);
- gOverheadTime = MILLION + TIME.tmCount;
- /*
- * Restart the timer
- */
- InsXTime(&TIME);
- PrimeTime(&TIME, 0);
- }
- }
- exit: return (status);
- }
-
- /*
- * GetEpoch returns the current extended time of day.
- * It requires the drift-free time manager. See the
- * Time Manager discussion in Inside Mac VI for details
- * of the procedure.
- */
- void
- GetEpoch(
- MicrosecondEpochPtr result
- )
- {
- RmvTime(&TIME); /* Stop Clock */
- result->time = gTimeInfo.epoch; /* Get seconds */
- /*
- * TIME.tmCount contains the residual number of
- * microseconds. This is a negative number (see
- * IM-VI). The following, then, computes the
- * number of microseconds that have elapsed in
- * the current second.
- */
- result->microsecond =
- (MILLION + TIME.tmCount) /* Offset "now" */
- - gOverheadTime; /* - call cost */
- if (result->microsecond < 0) { /* New second? */
- --result->time; /* Correct it. */
- result->microsecond += MILLION;
- }
- InsXTime(&TIME); /* Drift-free */
- PrimeTime(&TIME, 0); /* Timer start */
- }
-
- /*
- * Return the difference between two (nearby) epochs.
- * The result is in microseconds and has a range of
- * up to about 35 minutes.
- *
- * DeltaTime returns TRUE if deltaTime is valid.
- */
- Boolean
- DeltaTime(
- MicrosecondEpochPtr epoch1,
- MicrosecondEpochPtr epoch2,
- signed long *deltaTime
- )
- {
- long seconds;
- long microseconds;
-
- seconds = epoch2->time - epoch1->time;
- microseconds =
- epoch2->microsecond - epoch1->microsecond;
- *deltaTime = (seconds * MILLION) + microseconds;
- /*
- * The result is valid only if the
- * absolute value of the difference is
- * less than about 35 minutes. I.e.
- * 2^31 <= (35 * 60 * 10^6)
- */
- if (seconds < 0)
- seconds = (-seconds);
- return (seconds <= (34 * 60));
- }
-
- /*
- * This local function formats hour:minute:second.
- */
- static void
- FormatTimeString(
- StringPtr result,
- long what,
- Boolean needColon
- )
- {
- Str255 value;
-
- if (needColon)
- result[++result[0]] = ':';
- NumToString(what, value);
- if (value[0] == 1)
- result[++result[0]] = '0';
- Concat(result, value);
- }
-
- /*
- * Convert the time of day to a consistent, fixed-width
- * format of hh:mm:ss.microseconds. This is always in
- * 24 hour format.
- */
- void
- EpochToString(
- MicrosecondEpochPtr epochPtr,
- StringPtr result
- )
- {
- unsigned int i;
- DateTimeRec now;
- Str255 value;
-
- Secs2Date(epochPtr->time, &now);
- result[0] = 0;
- FormatTimeString(result, now.hour, FALSE);
- FormatTimeString(result, now.minute, TRUE);
- FormatTimeString(result, now.second, TRUE);
- NumToString(
- epochPtr->microsecond + MILLION,
- value
- );
- value[1] = '.';
- Concat(result, value);
- }
-
- /*
- * String concatenator for Pascal strings.
- */
- static void
- Concat(
- StringPtr dst,
- StringPtr src
- )
- {
- short copySize;
-
- copySize = src[0];
- if ((copySize + dst[0]) > 255)
- copySize = 255 - dst[0];
- BlockMove(
- &src[1],
- &dst[dst[0] + 1],
- (long) copySize
- );
- dst[0] += copySize;
- }
-
- /*
- * Adjust the clock by adding the adjustment to the
- * current clock. There is a built-in delay to
- * make sure our timer task gets to do its thing.
- *
- * Note: the right way to do this is to change the system
- * clock tick base from 1000000 and continually adjust
- * the clock a bit every second until it's right.
- * Unfortunately, we don't have access to the system
- * clock time manager record.
- */
- void
- AdjustClock(
- long adjustment
- )
- {
- MicrosecondEpoch ourEpoch;
- long timeOfDay;
-
- GetEpoch(&ourEpoch);
- ourEpoch.time += (adjustment / MILLION);
- adjustment %= MILLION;
- if (ourEpoch.microsecond >= (MILLION / 2))
- ++ourEpoch.time;
- else if (ourEpoch.microsecond <= (-(MILLION / 2)))
- --ourEpoch.time;
- SetDateTime(ourEpoch.time);
- /*
- * Vamp until our shadow clock has a chance to
- * update the local value.
- */
- GetEpoch(&ourEpoch);
- timeOfDay = ourEpoch.time;
- do {
- GetEpoch(&ourEpoch);
- } while (timeOfDay == ourEpoch.time);
- }
-
- /*
- * This will be called automatically by the
- * ExitToShell trap.
- */
- void
- CancelMicrosecondTimer()
- {
-
- #if 0 /* Enable this to put a debug trap here */
- asm {
- nop
- }
- #endif
- if (TIME.tmAddr != NULL) {
- RmvTime(&TIME);
- TIME.tmAddr = NULL;
- }
- if (oldExitVector != NULL) {
- SetTrapAddress(
- (long) oldExitVector,
- _ExitToShell
- );
- oldExitVector = NULL;
- if (jumpVector != NULL) {
- DisposPtr(jumpVector);
- jumpVector = NULL;
- }
- }
- }
-
- /*
- * This is called by the ExitToShell trap.
- * It cancels the timer service and removes
- * itself from the trap process, then re-calls
- * ExitToShell to allow other trap handlers to
- * execute.
- */
- static void
- TimerExitHandler()
- {
- long oldA5 = SetCurrentA5();
-
- CancelMicrosecondTimer();
- SetA5(oldA5);
- ExitToShell(); /* Call next exit handler */
- }
-
- /*
- * This private routine is called by the TimeManager at
- * every clock tick. There is blood on every line of this
- * function -- and on a number of lines of code that aren't
- * here any more. This function will need to be rewritten
- * for MPW-C, as that compiler lacks an asm statement.
- */
- static pascal void
- TimeCounter()
- {
- asm {
- /*
- * When we are called, A1 -> the time info
- * record which we have extended with our
- * "time of day" shadow. Update it with the
- * current system time-of-day value (so that
- * we remain coordinated with any changes
- * caused by SetDateTime or Control Panel
- * calls). This may mean that we are up to
- * one second out of step from the system, but
- * this probably can't be helped. TimeLM is
- * the system global "time of day" variable.
- * This variable has a different name in MPW-C.
- */
- move.l TimeLM, \
- OFFSET(TimeInfoRecord,epoch)(a1)
- move.l a1,a0 ;; a0 = TmTaskPtr
- move.l #-MILLION,d0 ;; d0 = count
- dc.w 0xA05A ;; _PrimeTime
- }
- }
-
-